package top.linuyx.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import top.linuyx.exception.GlobalException;
import top.linuyx.mapper.UserMapper;
import top.linuyx.pojo.param.VerificationCodeSimpleMailParam;
import top.linuyx.service.VerificationCodeService;
import top.linuyx.utils.ApplicationContextUtils;
import top.linuyx.utils.MallUtils;

import javax.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @Author Linuyx
 * @Description VerificationCodeService实现类 处理:生成验证码、判断是否重复发送验证码、发送验证码等业务
 * @Date Created in 2021-04-25 18:48
 */

@Service
public class VerificationCodeServiceImpl implements VerificationCodeService {

    /**
     * 验证码有效时间(5分钟|300秒)
     */
    private final int EFFECTIVE_TIME = 300;

    /**
     * 60秒内不允许重复发送验证码
     */
    private final int REJECT_SEND = 60;

    /**
     * 发送者邮箱
     */
    @Value("${spring.mail.username}")
    private String sendEmail;

    /**
     * 绑定邮箱RedisKey格式
     */
    @Value("${verification-code.bind-mail}")
    private String bindMailKey;

    /**
     * 修改密码RedisKey格式
     */
    @Value("${verification-code.update-password}")
    private String updatePasswordKey;

    @Resource
    private JavaMailSender javaMailSender;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private UserMapper userMapper;

    /**
     * 生成六位验证码(数字+字母)
     *
     * @return
     */
    private String getVerificationCode() {
        return UUID.randomUUID().toString().replace("-", "").substring(0, 6);
    }

    /**
     * 规定时间内不允许重复发送验证码
     */
    private void rejectSend(String key, String username) {
        //规定时间内不允许重复发送验证码
        Long expireTime = redisTemplate.getExpire(key + username);
        if (expireTime != null && EFFECTIVE_TIME - expireTime <= REJECT_SEND) {
            throw new GlobalException("60秒内不允许重复发送验证码");
        }
    }

    /**
     * 发送绑定邮箱验证码(验证发送要求)
     *
     * @param simpleMailParam 简单邮件请求体
     */
    @Override
    public void sendBindEmailVerificationCode(VerificationCodeSimpleMailParam simpleMailParam) {
        //获得当前登录的用户
        String username = MallUtils.getUser();

        //规定时间内不允许重复发送验证码
        rejectSend(bindMailKey, username);

        //生成验证码
        String verificationCode = getVerificationCode();
        //设置邮件主题、正文
        simpleMailParam.setSubject("[Mall]验证电子邮件");
        simpleMailParam.setText(
                "[Mall]欢迎你\r\n" +
                "提示:你正在验证电子邮件\r\n" +
                "验证码:" + verificationCode +
                 "\r\n*五分钟内有效*"
        );

        //调用异步方法,发送验证码(不能直接调用本类方法,异步注解会失效 解决:用SpringAOP动态代理的对象来调用异步方法)
        VerificationCodeService verificationCodeServiceImpl =
                (VerificationCodeService) ApplicationContextUtils.getBean("verificationCodeServiceImpl");
        verificationCodeServiceImpl.sendVerificationCodeAsync(bindMailKey, username, verificationCode, simpleMailParam);
    }

    /**
     * 发送修改密码验证码(验证发送要求)
     *
     * @param simpleMailParam 简单邮件请求体
     */
    @Override
    public void sendUpdatePasswordVerificationCode(VerificationCodeSimpleMailParam simpleMailParam) {
        //获得当前登录的用户
        String username = MallUtils.getUser();

        //规定时间内不允许重复发送验证码
        rejectSend(updatePasswordKey, username);

        //根据用户名查询邮箱
        String receiveEmail = userMapper.getEmailByUsername(username);
        simpleMailParam.setReceiveEmail(receiveEmail);

        //生成验证码
        String verificationCode = getVerificationCode();
        //设置邮件主题、正文
        simpleMailParam.setSubject("[Mall]修改密码安全验证");
        simpleMailParam.setText(
                "[Mall]欢迎你\r\n" +
                        "提示:你正在进行修改密码安全验证\r\n" +
                        "验证码:" + verificationCode +
                        "\r\n*五分钟内有效*"
        );

        //调用异步方法,发送验证码(不能直接调用本类方法,异步注解会失效 解决:用SpringAOP动态代理的对象来调用异步方法)
        VerificationCodeService verificationCodeServiceImpl =
                (VerificationCodeService) ApplicationContextUtils.getBean("verificationCodeServiceImpl");
        verificationCodeServiceImpl.sendVerificationCodeAsync(updatePasswordKey, username, verificationCode, simpleMailParam);
    }

    /**
     *
     * @param key RedisKey格式
     * @param username 当前登录用户
     * @param verificationCode 验证码
     * @param simpleMailParam 简单邮件请求体
     */
    @Async
    @Override
    public void sendVerificationCodeAsync(String key, String username, String verificationCode, VerificationCodeSimpleMailParam simpleMailParam) {
        //设置邮件接收者、主题、正文、发送者
        SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
        simpleMailMessage.setTo(simpleMailParam.getReceiveEmail());
        simpleMailMessage.setSubject(simpleMailParam.getSubject());
        simpleMailMessage.setText(simpleMailParam.getText());
        simpleMailMessage.setFrom(sendEmail);

        //发送邮件
        javaMailSender.send(simpleMailMessage);

        //判断是否是绑定邮箱,如果是绑定邮箱需要在验证码前面拼接上邮箱,用于判断绑定邮件和发送邮件是同一个
        if(key.equals(bindMailKey)){
            verificationCode = simpleMailParam.getReceiveEmail()+":"+verificationCode;
        }

        //将验证码存入redis并设置五分钟过期
        redisTemplate.opsForValue().set(key + username, verificationCode, EFFECTIVE_TIME, TimeUnit.SECONDS);
    }

}
